#!/usr/bin/env bash

include "locker.sh"
include "objectv5.sh"
include "test.sh"

ObjectV5::DefineClass DatabaseV3 "
    --cargs=path
    --string=path
"

function DatabaseV3::DatabaseV3()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local res="0"
    if ! [ -e "$path" ]; then
        mkdir -p "$path"
        res="$?"
    fi
    return "$res"
}

function DatabaseV3::IsObject()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 0 )); do
        paths+="/$1"; shift
    done

    [ -d "$paths" ]
    return
}

function DatabaseV3::IsArray()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 0 )); do
        paths+="/$1"; shift
    done

    [ -f "$paths/0" ]
    return
}

function DatabaseV3::IsExist()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 0 )); do
        paths+="/$1"; shift
    done

    [ -e "$paths" ]
}

if ${SHELL_DEBUG:-false}; then
function DatabaseV3::GetVal()
{
    if DatabaseV3::IsObject "$@"; then
        assert_fail "DatabaseV3::GetVal 无法获取对象的值: '$*'"
        return 1
    fi

    if ! DatabaseV3::IsExist "$@"; then
        assert_fail "DatabaseV3::GetVal 对象不存在: '$*'"
        return 1
    fi

    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 0 )); do
        paths+="/$1"; shift
    done

    cat "$paths"
    return
}
else
function DatabaseV3::GetVal()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 0 )); do
        paths+="/$1"; shift
    done

    cat "$paths"
    return
}
fi

function DatabaseV3::SetVal()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 2 )); do
        paths+="/$1"; shift
    done

    local file="$1"; shift
    local content="$1"; shift
    FileLocker locker{"$path"}
    mkdir -p "$paths"
    if [ -d "$paths/$file" ]; then
        assert_fail "DatabaseV3::SetVal 目标是一个对象: '$paths/$file'"
        return 1
    fi

    echo "$content" > "$paths/$file"
    return
}

function DatabaseV3::DelVal()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 0 )); do
        paths+="/$1"; shift
    done

    FileLocker locker{"$path"}
    if [ -e "$paths" ]; then
        rm -rf "$paths"
        local res="$?"
    fi
    return "$res"
}

function DatabaseV3::EraseKey()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 1 )); do
        paths+="/$1"; shift
    done
    local index="$1"; shift

    FileLocker locker{"$path"}
    if [ -e "$paths" ]; then
        debuglog "remove $paths/$index"
        rm -rf "$paths/$index"
        local i j
        for (( i = index, j = i + 1; ; i++, j++ )); do
            if ! [ -e "$paths/$j" ]; then
                debuglog "not exist $j"
                break
            fi

            debuglog "move $j $i"
            mv "$paths/$j" "$paths/$i"
        done
    fi
    return
}

function DatabaseV3::EraseVal()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 1 )); do
        paths+="/$1"; shift
    done
    local value="$1"; shift

    FileLocker locker{"$path"}
    if [ -e "$paths" ]; then
        local not_exist=false
        cd "$paths"
        debuglog "$(ls)"
        for index in $(ls); do
            if [ "$(cat "$paths/$index")" == "$value" ]; then
                not_exist=true
                break
            fi
        done
        cd - >/dev/null

        if $not_exist; then
            debuglog "not found"
            return 1
        fi

        debuglog "remove $paths/$index"
        rm -rf "$paths/$index"
        local i j
        for (( i = index, j = i + 1; ; i++, j++ )); do
            if ! [ -e "$paths/$j" ]; then
                debuglog "not exist $j"
                break
            fi

            debuglog "move $j $i"
            mv "$paths/$j" "$paths/$i"
        done
    fi
    return 0
}

function DatabaseV3::GetLen()
{
    local this="$1"; shift
    local keys=($(DatabaseV3::GetKeys "$this" "$@"))
    echo "${#keys[*]}"
    return
}

function DatabaseV3::GetKeys()
{
    local this="$1"; shift
    local path="$(DatabaseV3::path "$this")"
    local paths="$path"
    while (( $# > 0 )); do
        paths+="/$1"; shift
    done
    ls "$paths"
    return
}

function DatabaseV3::Test1()
{
    local testdb="$(mktemp --dry-run "/tmp/databasev3.test1.XXXXXXXX")"
                              DatabaseV3          db{"$testdb"}

    # set/get/del
    test::should_return_false DatabaseV3::IsExist db global_data docker_version
                              DatabaseV3::SetVal  db global_data docker_version "15"
    test::should_return_true  DatabaseV3::IsExist db global_data docker_version
    test::run_cmd             DatabaseV3::GetVal  db global_data docker_version
    test::should_be           "15"
                              DatabaseV3::DelVal  db global_data docker_version
    test::should_return_false DatabaseV3::IsExist db global_data docker_version

    # set/set/get
    test::should_return_false DatabaseV3::IsExist db updater new
                              DatabaseV3::SetVal  db updater new "true"
                              DatabaseV3::SetVal  db updater new "false"
    test::should_return_true  DatabaseV3::IsExist db updater new
    test::run_cmd             DatabaseV3::GetVal  db updater new
    test::should_be           "false"

    # len
    test::should_return_false DatabaseV3::IsExist db repos
                              DatabaseV3::SetVal  db repos 0 name m1
                              DatabaseV3::SetVal  db repos 0 path path/m1
    test::should_return_true  DatabaseV3::IsExist db repos
    test::should_return_true  DatabaseV3::IsExist db repos 0
    test::should_return_true  DatabaseV3::IsExist db repos 0 name
    test::should_return_true  DatabaseV3::IsExist db repos 0 path
    test::should_return_false DatabaseV3::IsExist db repos 1
    test::run_cmd             DatabaseV3::GetLen  db repos
    test::should_be           "1"
    test::run_cmd             DatabaseV3::GetKeys db repos
    test::should_be           "0"

    rm -rf "$testdb"
    return
}
test::register_test DatabaseV3::Test1

function DatabaseV3::Test2()
{
    local testdb="$(mktemp --dry-run "/tmp/databasev3.test2.XXXXXXXX")"
                              DatabaseV3           db{"$testdb"}

    # pre
    test::should_return_false DatabaseV3::IsExist  db repos
                              DatabaseV3::SetVal   db repos 0 name m1
                              DatabaseV3::SetVal   db repos 0 path path/m1
                              DatabaseV3::SetVal   db repos 1 name m2
                              DatabaseV3::SetVal   db repos 1 path path/m2
                              DatabaseV3::SetVal   db repos 2 name m3
                              DatabaseV3::SetVal   db repos 2 path path/m3
    test::should_return_true  DatabaseV3::IsExist  db repos
    test::should_return_true  DatabaseV3::IsExist  db repos 2
    test::should_return_true  DatabaseV3::IsExist  db repos 2 name
    test::should_return_true  DatabaseV3::IsExist  db repos 2 path
    test::should_return_false DatabaseV3::IsExist  db repos 3
    test::run_cmd             DatabaseV3::GetVal   db repos 1 name
    test::should_be           "m2"
    test::run_cmd             DatabaseV3::GetVal   db repos 1 path
    test::should_be           "path/m2"
    test::run_cmd             DatabaseV3::GetLen   db repos
    test::should_be           "3"
    test::run_cmd             DatabaseV3::GetKeys  db repos
    test::should_be           "$(sed 's@ @\n@g' <<< "0 1 2")"

    # erase
                              DatabaseV3::EraseKey db repos 1
    test::should_return_true  DatabaseV3::IsExist  db repos
    test::should_return_true  DatabaseV3::IsExist  db repos 1
    test::should_return_true  DatabaseV3::IsExist  db repos 1 name
    test::should_return_true  DatabaseV3::IsExist  db repos 1 path
    test::should_return_false DatabaseV3::IsExist  db repos 2
    test::run_cmd             DatabaseV3::GetVal   db repos 1 name
    test::should_be           "m3"
    test::run_cmd             DatabaseV3::GetVal   db repos 1 path
    test::should_be           "path/m3"
    test::run_cmd             DatabaseV3::GetLen   db repos
    test::should_be           "2"
    test::run_cmd             DatabaseV3::GetKeys  db repos
    test::should_be           "$(sed 's@ @\n@g' <<< "0 1")"

    rm -rf "$testdb"
    return
}
test::register_test DatabaseV3::Test2
